package sf.database.mapper;

import sf.spring.boot.system.JavaVersion;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 接口default方法调用
 */
public class DefaultMethodInvoke {
    private static final Map<Method, MethodHandle> methodCache = new ConcurrentHashMap<>();
    private static Constructor<MethodHandles.Lookup> constructor;
    private static final boolean jdk8 = JavaVersion.getJavaVersion() == JavaVersion.EIGHT;

    static {
        if (jdk8) {
            try {
                constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
                constructor.setAccessible(true);
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private static MethodHandle getMethodHandle(Method method) throws Exception {
        Class<?> clazz = method.getDeclaringClass();
        if (jdk8) {
            return constructor.newInstance(clazz)
                    .in(clazz)
                    .unreflectSpecial(method, clazz);
        } else {
            //高于java8？
            return MethodHandles.lookup()
                    .findSpecial(
                            clazz,
                            method.getName(),
                            MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
                            clazz
                    );
        }
    }


    /**
     * 默认方法调用
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    public static Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
        //https://dzone.com/articles/correct-reflective-access-to-interface-default-methods
        //https://gist.github.com/lukaseder/f47f5a0d156bf7b80b67da9d14422d4a
        MethodHandle handle = methodCache.computeIfAbsent(method, k -> {
            try {
                return getMethodHandle(method);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        return handle.bindTo(proxy).invokeWithArguments(args);
    }
}
